import ast
import re
from domain import *
from parserbasic import UnknownNameVisitor

class DelayedDomain:
    """A domain that depends on delayed execution of code.  The
    bind() method must be called with the new environment before executing"""
    def __init__(self,env,code,deps):
        self.env = env
        self.code = code
        self.deps = deps
        self.object = None

    def bind(self,newEnv):
        e = self.env.copy();
        e.update(newEnv)
        self.object = DomainParser.parse(e,self.code)
    
    def dimensionality(self):
        return self.object.dimensionality()

    def bounds(self):
        return self.object.bounds()
    
    def contains(self,x):
        return self.object.contains(x)
    
    def sample(self):
        return self.object.sample()


class DomainParser:
    @staticmethod
    def parse(env,code,delayDict=None):
        code = code.strip()
        DomainParser.code_check(code)
        
        # 1-D discrete domain
        if code.startswith('{'):
            return DomainParser.parse_discrete(env, code, delayDict)
        # contains a:b discrete range
        elif DomainParser.is_colon_range(code):
            return DomainParser.parse_colon_range(code)
        # code may contain delayed object
        else:
            try:
                res = eval(code,globals(),env)
                return Domain(res)
            except NameError as e:
                deps = DomainParser.delay_dependencies(env,code,delayDict)
                if deps:
                    return DomainParser.parse_delayed(env,code,deps)
                # code is a normal domain
                else:
                    print e
                    raise RuntimeError("Weird, a name error but no detected dependencies?")

    @staticmethod
    def code_check(code):
        return True

    @staticmethod
    def delay_dependencies(env,code,delayDict):
        if delayDict == None: return []
        codeAST = ast.parse(code, mode='eval')
        unknownTester = UnknownNameVisitor(env)
        unknownTester.visit(codeAST)
        if len(unknownTester.unknownNames)==0:
            return []

        deps = []
        for name in unknownTester.unknownNames:
            try:
                eval(name,env)
            except NameError:
                if name not in delayDict:
                    raise ValueError("Code contains a name "+name+" that is not defined either in the environment or in the delay code")
                deps.append(name)
        return deps

    @staticmethod
    def parse_delayed(env, code, deps):
        return DelayedDomain(env,code,deps)

    @staticmethod
    def parse_discrete(env, code, delayDict=None):
        if not (code[0] == '{' and code[-1] == '}'):
            raise RuntimeError("Discrete domains must be delineated by curly "\
                                "braces: " + code)
        code = '[' + code[1:-1] + ']'
        #code = fix_ranges(code)
        try:
            domain = eval(code, globals(), env)
            return DiscreteDomain(domain)
        except NameError:
            deps = DomainParser.delay_dependencies(env,code,delayDict)
            if deps:
                return DelayedDomain(env,code,deps)
            else:
                    raise RuntimeError("Weird, a name error but no detected dependencies?")

    @staticmethod
    def is_colon_range(code):
        intRE = re.compile('[+-]?\d+');
        colonRE = re.compile('\s*(%s)\s*:\s*(%s)\s*' % (intRE.pattern,intRE.pattern))
        return colonRE.match(code)!=None

    @staticmethod
    def parse_colon_range(code):
        intRE = re.compile('[+-]?\d+');
        colonRE = re.compile('\s*(%s)\s*:\s*(%s)\s*' % (intRE.pattern,intRE.pattern))
        match = colonRE.match(code)
        assert match!=None
        return DiscreteRangeDomain(int(match.group(1)),int(match.group(2)))

def five():
    return 5

def plusone(x):
    return x+1

if __name__== "__main__":
    a = Domain((0,1))
    b = Domain([0,1,2,6])
    c = Domain(xrange(4,7))
    d = Domain([(0,1),(1,2),(3,4)])
    e = Domain([(0,1),(1,2),[0,2,3,4]])
    f = Domain([(0,1),(1,2),[0,2,3,4],[(4,5),(6,7)]])
    g = DomainParser.parse(globals(),'(0,1)')
    h = DomainParser.parse(globals(),'[0,1,2,6]')
    i = DomainParser.parse(globals(),'{0,1,2,6}')
    j = DomainParser.parse(globals(),'(0,five())')
    k = DomainParser.parse(globals(),'4:6')
    #l = DomainParser.parse(globals(),'4:five()')
    m = DomainParser.parse(globals(),'{4,plusone(x),plusone(6)}',{'x':4})
    assert(isinstance(a,ContinuousDomain))
    assert(isinstance(b,DiscreteDomain))
    assert(isinstance(c,DiscreteRangeDomain))
    assert(isinstance(d,CompositeDomain))
    assert(isinstance(e,CompositeDomain))
    assert(isinstance(f,CompositeDomain))
    assert(isinstance(g,ContinuousDomain))
    assert(isinstance(h,DiscreteDomain))
    assert(isinstance(i,DiscreteDomain))
    assert(isinstance(j,ContinuousDomain))
    assert(isinstance(k,DiscreteRangeDomain))
    #assert(isinstance(l,DiscreteRangeDomain))
    assert(isinstance(m,DelayedDomain))
    print a.bounds()
    print b.bounds()
    print j.bounds()
    m.bind({'x':10})
    print m.bounds()
